/*******************************************************************************
 * Copyright 2011 Towee.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package net.towee.client.managers.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import net.towee.client.event.UserAccepted;
import net.towee.client.event.core.AccountIdentityUpdatedEvent;
import net.towee.client.event.core.AccountProfileUpdatedEvent;
import net.towee.client.event.core.ErrorWhileInitEvent;
import net.towee.client.event.core.UserOnlineStateUpdated;
import net.towee.client.managers.AccountHelper;
import net.towee.client.managers.AccountsManager;
import net.towee.model.account.AccountData;
import net.towee.model.account.AccountIdentity;
import net.towee.model.account.AccountProfile;
import net.towee.model.utils.Pair;
import net.towee.server.rpc.services.ToweeServiceAsync;

import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;

/**
 * Account manager provides getting and storing account data. It contain methods
 * for initialize logged in account and getting some other accounts.
 * 
 * @author viiv.c58@gmail.com (Artem Artem'ev)
 */
public class AccountsManagerImpl implements AccountsManager {
	// Caches
	private Map<String, AccountIdentity> identityCache;
	private Map<String, AccountProfile> profileCache;

	// Injected objects
	private final ToweeServiceAsync service;
	private final HandlerManager eventBus;
	private final AccountHelper helper;
	private final ConcurrentMap<String, List<AccountIdentity>> searchCache;

	@Inject
	public AccountsManagerImpl(ToweeServiceAsync service, HandlerManager eventBus,
			AccountHelper accountHelper) {
		this.service = service;
		this.eventBus = eventBus;
		this.helper = accountHelper;

		this.profileCache = new HashMap<String, AccountProfile>();
		this.identityCache = new HashMap<String, AccountIdentity>();
		this.searchCache = new MapMaker().maximumSize(40).expireAfterWrite(10, TimeUnit.MINUTES)
				.makeMap();

		handelPushEvents();
	}

	/**
	 * Create handlers for push events
	 */
	private void handelPushEvents() {
		service.onAccountIdentityUpdated(null, null, new AsyncCallback<AccountIdentity>() {
			@Override
			public void onFailure(Throwable caught) {
			}

			@Override
			public void onSuccess(AccountIdentity result) {
				if (!result.getId().equals(helper.getAccount().getId())) {
					// Update cache
					putIdentityToCache(result);

					// Fire an event
					eventBus.fireEvent(new AccountIdentityUpdatedEvent(result));
				}
			}
		});

		service.onAccountProfileUpdated(null, null, new AsyncCallback<AccountProfile>() {
			@Override
			public void onFailure(Throwable caught) {
			}

			@Override
			public void onSuccess(AccountProfile result) {
				if (!result.getIdentity().getId().equals(helper.getAccount().getId())) {
					// Update cache
					putProfileToCache(result);

					// Fire an event
					eventBus.fireEvent(new AccountProfileUpdatedEvent(result));
				}
			}
		});

		service.onOnlineStateChanged(null, null, new AsyncCallback<Pair<String, Boolean>>() {
			@Override
			public void onFailure(Throwable caught) {
			}

			@Override
			public void onSuccess(Pair<String, Boolean> result) {
				// Update cache
				if (identityCache.containsKey(result.getFirst()))
					identityCache.get(result.getFirst()).setOnlineState(result.getSecond());

				// Fire the event
				eventBus.fireEvent(new UserOnlineStateUpdated(result));
			}

		});
	}

	/**
	 * Put identity to cache
	 * 
	 * @param identity
	 */
	public void putIdentityToCache(AccountIdentity identity) {
		Preconditions.checkNotNull(identity);
		Preconditions.checkNotNull(identity.getId());

		identityCache.put(identity.getId(), identity);
	}

	public void putProfileToCache(AccountProfile profile) {
		Preconditions.checkNotNull(profile);
		Preconditions.checkNotNull(profile.getIdentity().getId());

		profileCache.put(profile.getIdentity().getId(), profile);
	}

	@Override
	public void initializeAccount() {
		service.getCurrentAccount(new AsyncCallback<AccountData>() {

			@Override
			public void onFailure(Throwable caught) {
				eventBus.fireEvent(new ErrorWhileInitEvent());
			}

			@Override
			public void onSuccess(AccountData result) {
				// Put to cache data
				putIdentityToCache(result.getAccountIdentity());
				putProfileToCache(result.getProfile());

				// Fire an event
				eventBus.fireEvent(new UserAccepted(result));
			}

		});
	}

	@Override
	public void getAccountProfile(String accountId, final AsyncCallback<AccountProfile> handler) {
		if (profileCache.containsKey(accountId))
			handler.onSuccess(profileCache.get(accountId));
		else
			service.getAccountProfile(accountId, new AsyncCallback<AccountProfile>() {
				@Override
				public void onFailure(Throwable caught) {
				}

				@Override
				public void onSuccess(AccountProfile result) {
					putProfileToCache(result);
					handler.onSuccess(result);
				}
			});
	}

	@Override
	public void getAccountIdentity(String accountId, final AsyncCallback<AccountIdentity> handler) {
		if (identityCache.containsKey(accountId))
			handler.onSuccess(identityCache.get(accountId));
		else
			service.getAccountIdentity(accountId, new AsyncCallback<AccountIdentity>() {
				@Override
				public void onFailure(Throwable caught) {
				}

				@Override
				public void onSuccess(AccountIdentity result) {
					putIdentityToCache(result);
					handler.onSuccess(result);
				}
			});
	}

	@Override
	public void checkAccountWithNicknameExists(String nickname, AsyncCallback<Boolean> handler) {
		service.checkAccountWithNicknameExists(nickname, handler);
	}

	@Override
	public void findAccount(String str, int offset, int limit,
			final AsyncCallback<List<AccountIdentity>> handler) {
		// Create identity of this request
		final String reqIdnt = str + offset + limit;

		// Check search cache
		if (searchCache.containsKey(reqIdnt))
			handler.onSuccess(searchCache.get(reqIdnt));
		else
			service.findAccount(str, offset, limit, new AsyncCallback<List<AccountIdentity>>() {

				@Override
				public void onFailure(Throwable caught) {
				}

				@Override
				public void onSuccess(List<AccountIdentity> result) {
					Preconditions.checkNotNull(result, "fail account search");

					// Put result to cache
					searchCache.put(reqIdnt, result);

					// Invoke handler
					handler.onSuccess(result);
				}

			});
	}
}
